// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Microsoft.AspNetCore.Razor.Language.Components;

namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
    public sealed class ComponentAttributeIntermediateNode : IntermediateNode
    {
        public ComponentAttributeIntermediateNode()
        {
        }

        public ComponentAttributeIntermediateNode(TagHelperHtmlAttributeIntermediateNode attributeNode)
        {
            if (attributeNode == null)
            {
                throw new ArgumentNullException(nameof(attributeNode));
            }

            AttributeName = attributeNode.AttributeName;
            AttributeStructure = attributeNode.AttributeStructure;
            Source = attributeNode.Source;

            for (var i = 0; i < attributeNode.Children.Count; i++)
            {
                Children.Add(attributeNode.Children[i]);
            }

            for (var i = 0; i < attributeNode.Diagnostics.Count; i++)
            {
                Diagnostics.Add(attributeNode.Diagnostics[i]);
            }
        }

        public ComponentAttributeIntermediateNode(TagHelperPropertyIntermediateNode propertyNode)
        {
            if (propertyNode == null)
            {
                throw new ArgumentNullException(nameof(propertyNode));
            }

            var attributeName = propertyNode.AttributeName;

            AttributeName = attributeName;
            AttributeStructure = propertyNode.AttributeStructure;
            BoundAttribute = propertyNode.BoundAttribute;
            PropertyName = propertyNode.BoundAttribute.GetPropertyName();
            Source = propertyNode.Source;
            TagHelper = propertyNode.TagHelper;
            TypeName = propertyNode.BoundAttribute.IsWeaklyTyped() ? null : propertyNode.BoundAttribute.TypeName;

            for (var i = 0; i < propertyNode.Children.Count; i++)
            {
                Children.Add(propertyNode.Children[i]);
            }

            for (var i = 0; i < propertyNode.Diagnostics.Count; i++)
            {
                Diagnostics.Add(propertyNode.Diagnostics[i]);
            }
        }

        public ComponentAttributeIntermediateNode(TagHelperDirectiveAttributeIntermediateNode directiveAttributeNode)
        {
            if (directiveAttributeNode == null)
            {
                throw new ArgumentNullException(nameof(directiveAttributeNode));
            }

            AttributeName = directiveAttributeNode.AttributeName;
            AttributeStructure = directiveAttributeNode.AttributeStructure;
            BoundAttribute = directiveAttributeNode.BoundAttribute;
            PropertyName = directiveAttributeNode.BoundAttribute.GetPropertyName();
            Source = directiveAttributeNode.Source;
            TagHelper = directiveAttributeNode.TagHelper;
            TypeName = directiveAttributeNode.BoundAttribute.IsWeaklyTyped() ? null : directiveAttributeNode.BoundAttribute.TypeName;

            for (var i = 0; i < directiveAttributeNode.Children.Count; i++)
            {
                Children.Add(directiveAttributeNode.Children[i]);
            }

            for (var i = 0; i < directiveAttributeNode.Diagnostics.Count; i++)
            {
                Diagnostics.Add(directiveAttributeNode.Diagnostics[i]);
            }
        }

        public ComponentAttributeIntermediateNode(TagHelperDirectiveAttributeParameterIntermediateNode directiveAttributeParameterNode)
        {
            if (directiveAttributeParameterNode == null)
            {
                throw new ArgumentNullException(nameof(directiveAttributeParameterNode));
            }

            AttributeName = directiveAttributeParameterNode.AttributeNameWithoutParameter;
            AttributeStructure = directiveAttributeParameterNode.AttributeStructure;
            BoundAttribute = directiveAttributeParameterNode.BoundAttribute;
            PropertyName = directiveAttributeParameterNode.BoundAttributeParameter.GetPropertyName();
            Source = directiveAttributeParameterNode.Source;
            TagHelper = directiveAttributeParameterNode.TagHelper;
            TypeName = directiveAttributeParameterNode.BoundAttributeParameter.TypeName;

            for (var i = 0; i < directiveAttributeParameterNode.Children.Count; i++)
            {
                Children.Add(directiveAttributeParameterNode.Children[i]);
            }

            for (var i = 0; i < directiveAttributeParameterNode.Diagnostics.Count; i++)
            {
                Diagnostics.Add(directiveAttributeParameterNode.Diagnostics[i]);
            }
        }

        public override IntermediateNodeCollection Children { get; } = new IntermediateNodeCollection();

        public string AttributeName { get; set; }
        
        public AttributeStructure AttributeStructure { get; set; }

        public BoundAttributeDescriptor BoundAttribute { get; set; }

        public string PropertyName { get; set; }

        public TagHelperDescriptor TagHelper { get; set; }

        public string TypeName { get; set; }

        public string GloballyQualifiedTypeName { get; set; }

        public override void Accept(IntermediateNodeVisitor visitor)
        {
            if (visitor == null)
            {
                throw new ArgumentNullException(nameof(visitor));
            }

            visitor.VisitComponentAttribute(this);
        }

        public override void FormatNode(IntermediateNodeFormatter formatter)
        {
            if (formatter == null)
            {
                throw new ArgumentNullException(nameof(formatter));
            }
            
            formatter.WriteContent(AttributeName);

            formatter.WriteProperty(nameof(AttributeName), AttributeName);
            formatter.WriteProperty(nameof(AttributeStructure), AttributeStructure.ToString());
            formatter.WriteProperty(nameof(BoundAttribute), BoundAttribute?.DisplayName);
            formatter.WriteProperty(nameof(PropertyName), PropertyName);
            formatter.WriteProperty(nameof(TagHelper), TagHelper?.DisplayName);
            formatter.WriteProperty(nameof(TypeName), TypeName);
            formatter.WriteProperty(nameof(GloballyQualifiedTypeName), GloballyQualifiedTypeName);
        }

        public bool TryParseEventCallbackTypeArgument(out string argument)
        {
            // This is ugly and ad-hoc, but for various layering reasons we can't just use Roslyn APIs
            // to parse this. We need to parse this just before we write it out to the code generator,
            // so we can't compute it up front either.

            if (BoundAttribute == null || !BoundAttribute.IsEventCallbackProperty())
            {
                throw new InvalidOperationException("This attribute is not an EventCallback attribute.");
            }

            if (string.Equals(TypeName, ComponentsApi.EventCallback.FullTypeName, StringComparison.Ordinal))
            {
                // Non-Generic
                argument = null;
                return false;
            }

            if (TypeName != null && 
                TypeName.Length > ComponentsApi.EventCallback.FullTypeName.Length + "<>".Length &&
                TypeName.StartsWith(ComponentsApi.EventCallback.FullTypeName, StringComparison.Ordinal) &&
                TypeName[ComponentsApi.EventCallback.FullTypeName.Length] == '<' &&
                TypeName[TypeName.Length - 1] == '>')
            {
                // OK this is promising.
                //
                // Chop off leading `...EventCallback<` and let the length so the ending `>` is cut off as well.
                argument = TypeName.Substring(ComponentsApi.EventCallback.FullTypeName.Length + 1, TypeName.Length - (ComponentsApi.EventCallback.FullTypeName.Length + "<>".Length));
                return true;
            }

            // If we get here this is a failure. This should only happen if someone manages to mangle the name with extensibility.
            // We don't really want to crash though.
            argument = null;
            return false;
        }
    }
}
